/*
  AdViEmulator - AdventureVision emulator
  Copyright (C) 2012-2013  JustBurn

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPainter>
#include <QPaintEvent>
#include "memoryviewbox.h"

MemoryViewBox::MemoryViewBox(QWidget *parent) :
	QWidget(parent)
{
	avemu = 0;
	topAddr = 0;
	maxAddr = 0;
	resize(size());
	curAddr2 = 0;
	curMode = EDITSIDE_HEX;
	scrollProc = true;
}

void MemoryViewBox::attachEmu(AdViEmulator *emu, QScrollBar *sb)
{
	avemu = emu;
	viewsb = sb;
	connect(sb, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
}

void MemoryViewBox::setTypeMem(int typemem)
{
	if (!avemu) return;
	maxAddr = avemu->MemSize(typemem);
	if (topAddr >= maxAddr) topAddr = 0;
	if (curAddr2 >= maxAddr*2) curAddr2 = maxAddr*2-1;
	typeMem = typemem;
	topAddrLast = maxAddr - numLines * 16;
	if (topAddrLast < 0) topAddrLast = 0;
	viewsb->setMaximum(maxAddr / 16 - numLines);
	viewsb->setMinimum(0);
	moveCursor(curAddr2, true);
}

void MemoryViewBox::setTopAddress(int value)
{
	if (value < 0) value = 0;
	if (value >= topAddrLast) value = topAddrLast;
	topAddr = value;
	scrollProc = false;
	viewsb->setValue(topAddr / 16);
	scrollProc = true;
	repaint();
}

void MemoryViewBox::moveCursor2(int addr2)
{
	if (addr2 < 0) addr2 = 0;
	if (addr2 >= maxAddr*2) addr2 = maxAddr*2-1;
	curAddr2 = addr2;
	int EndAddr = topAddr + (numLines - 1) * 16;
	if (curAddr2 < topAddr*2) topAddr = curAddr2/2 & ~15;
	if (curAddr2 >= EndAddr*2) topAddr = (curAddr2/2 - (numLines - 1) * 16) & ~15;
	if (topAddr < 0) topAddr = 0;
	if (topAddr >= maxAddr*2) topAddr = maxAddr*2-1;
	scrollProc = false;
	viewsb->setValue(topAddr / 16);
	scrollProc = true;
}

void MemoryViewBox::moveCursor(int addr, bool refresh)
{
	moveCursor2(addr << 1);
	if (refresh) repaint();
}

void MemoryViewBox::paintEvent(QPaintEvent *)
{
	QPainter painter(this);
	QBrush cursor(hasFocus() ? QColor::fromRgb(255, 160, 64) : QColor::fromRgb(160, 160, 160), Qt::Dense4Pattern);
	QBrush cursoroff(QColor::fromRgb(160, 160, 160), Qt::Dense4Pattern);
	QImage previmage(16, 8, QImage::Format_RGB32);

	// Draw white background
	painter.setBrush(Qt::white);
	painter.setPen(Qt::transparent);
	painter.drawRect(0, 0, width(), height());
	painter.setBrush(Qt::transparent);
	painter.setPen(Qt::black);

	// Get metrics
	int twidth = painter.fontMetrics().width('0');
	int theight = painter.fontMetrics().height();

	// Draw memory content
	int addr = topAddr;
	{
		painter.setBrush(QColor::fromRgb(192, 160, 128));
		painter.setPen(QColor::fromRgb(128, 96, 64));
		painter.drawRect(0, 0, width() - 1, theight + 3);
		QString addrstr = QString::number(curAddr2 >> 1, 16).toUpper();
		addrstr = QString().fill('0', 4 - addrstr.length()) + addrstr;
		painter.setPen(QColor::fromRgb(255, 240, 224));
		painter.drawText(5, theight, addrstr);
		painter.setPen(QColor::fromRgb(64, 64, 64));
		for	(int i=0; i<16; i++) {
			QString datastr = QString::number(i, 16).toUpper();
			painter.drawText(5 + twidth/2 + twidth * (6 + i * 3), theight, datastr);
			painter.drawText(5 + twidth * (58 + i), theight, datastr);
		}
		painter.setBrush(Qt::transparent);
		painter.setPen(Qt::black);
	}
	for (int y=0; y<numLines+1; y++) {
		if (addr >= maxAddr) break;
		int ypos = (y + 2) * theight;
		QString addrstr = QString::number(addr, 16).toUpper();
		addrstr = QString().fill('0', 4 - addrstr.length()) + addrstr;
		painter.drawText(5, ypos, addrstr + ":");
		for (int x=0; x<16; x++) {
			// Draw watchpoint
			if (avemu->GetBreak(typeMem, addr)) {
				painter.setBrush(Qt::red);
				painter.setPen(Qt::transparent);
				painter.drawRect(5 + twidth * (6 + x * 3), ypos - theight + 4, twidth * 2, theight);
				painter.drawRect(5 + twidth * (58 + x), ypos - theight + 4, twidth, theight);
				painter.setBrush(Qt::transparent);
				painter.setPen(Qt::black);
			}

			// Draw cursor
			if ((curAddr2 >> 1) == addr) {
				painter.setBrush(cursor);
				painter.setPen(Qt::transparent);
				if (curMode == EDITSIDE_HEX) {
					painter.drawRect(5 + twidth * (6 + x * 3 + (curAddr2 & 1)), ypos - theight + 4, twidth, theight);
				} else {
					painter.drawRect(5 + twidth * (58 + x), ypos - theight + 4, twidth, theight);
				}
				painter.setBrush(cursoroff);
				if (curMode == EDITSIDE_ASCII) {
					painter.drawRect(5 + twidth * (6 + x * 3), ypos - theight + 4, twidth * 2, theight);
				} else {
					painter.drawRect(5 + twidth * (58 + x), ypos - theight + 4, twidth, theight);
				}
				painter.setBrush(Qt::transparent);
				painter.setPen(Qt::black);
			}

			// Draw byte
			quint8 data = 0xFF;
			if (avemu) data = avemu->MemRead(typeMem, addr);
			QChar datach = '.';
			if (data >= 32) datach = (QChar)data;
			QString datastr = QString::number(data, 16).toUpper();
			datastr = QString().fill('0', 2 - datastr.length()) + datastr;
			painter.drawText(5 + twidth * (6 + x * 3), ypos, datastr);
			painter.drawText(5 + twidth * (58 + x), ypos, datach);

			// Draw graphics
			for (int py=0; py<8; py++) {
				previmage.setPixel(x, py, data & (1 << (7 - py)) ? 0xEEEEEE : 0x000000);
			}

			// Next byte
			addr++;
			if (addr >= maxAddr) break;
		}
		painter.drawImage(5 + twidth * 54, ypos - 8, previmage);
	}
}

void MemoryViewBox::resizeEvent(QResizeEvent *event)
{
	numLines = event->size().height() / this->fontMetrics().height() - 1;
	topAddrLast = maxAddr - numLines * 16;
	if (topAddrLast < 0) topAddrLast = 0;
	viewsb->setMaximum(maxAddr / 16 - numLines);
	viewsb->setMinimum(0);
}

bool MemoryViewBox::writeCh2Mem(char ch)
{
	if (curMode == EDITSIDE_HEX) {
		int value = -1;
		if (ch == 0) value = 0;
		if ((ch >= '0') && (ch <= '9')) value = ch - '0';
		else if ((ch >= 'a') && (ch <= 'f')) value = ch - 'a' + 10;
		else if ((ch >= 'A') && (ch <= 'F')) value = ch - 'A' + 10;
		if (value >= 0) {
			uint8_t data = avemu->MemRead(typeMem, curAddr2 >> 1);
			if (curAddr2 & 1) data = (data & 0xF0) | value;
			else data = (data & 0x0F) | (value << 4);
			avemu->MemWrite(typeMem, curAddr2 >> 1, data);
			return true;
		}
	} else {
		avemu->MemWrite(typeMem, curAddr2 >> 1, ch);
		return true;
	}
	return false;
}

void MemoryViewBox::keyPressEvent(QKeyEvent *event)
{
	switch (event->key()) {
	case Qt::Key_Up:
		moveCursor2(curAddr2 - 32);
		break;
	case Qt::Key_Down:
		moveCursor2(curAddr2 + 32);
		break;
	case Qt::Key_Left:
		moveCursor2(curAddr2 - curMode);
		break;
	case Qt::Key_Right:
		moveCursor2(curAddr2 + curMode);
		break;
	case Qt::Key_PageUp:
		moveCursor2(curAddr2 - numLines/2*32);
		break;
	case Qt::Key_PageDown:
		moveCursor2(curAddr2 + numLines/2*32);
		break;
	case Qt::Key_Home:
		moveCursor2(0);
		break;
	case Qt::Key_End:
		moveCursor2(maxAddr*2-1);
		break;
	case Qt::Key_Enter:
	case Qt::Key_Return:
		if (curMode == EDITSIDE_HEX) curMode = EDITSIDE_ASCII;
		else if (curMode == EDITSIDE_ASCII) curMode = EDITSIDE_HEX;
		break;
	case Qt::Key_Insert:
		if (writeCh2Mem(0)) {
			moveCursor2(curAddr2 + curMode);
		}
		break;
	case Qt::Key_Delete:
		if (writeCh2Mem(0)) {
			moveCursor2(curAddr2 - curMode);
		}
		break;
	case Qt::Key_F6:
		if ((curAddr2 >= 0) && (curAddr2 < maxAddr*2)) {
			avemu->SetBreak(typeMem, curAddr2 >> 1, avemu->GetBreak(typeMem, curAddr2 >> 1) ? 0 : AdViEmulator::TRAP_ALL);
			repaint();
		}
		break;
	default:
		if (event->text().length()) {
			if (writeCh2Mem(event->text().toLatin1().at(0))) {
				moveCursor2(curAddr2 + curMode);
			}
		}
		break;
	}
	repaint();
}

void MemoryViewBox::wheelEvent(QWheelEvent *event)
{
	if (event->delta() > 0) {
		setTopAddress(topAddr - numLines/2*16);
	} else {
		setTopAddress(topAddr + numLines/2*16);
	}
}

void MemoryViewBox::mousePressEvent(QMouseEvent *event)
{
	int mcx = (event->pos().x() - 5) / this->fontMetrics().width('0');
	int mcy = (event->pos().y() - 5) / this->fontMetrics().height() - 1;
	if ((mcy >= 0) && (mcy < numLines)) {
		int addr2 = -1;
		if ((mcx >= 6) && (mcx <= 52)) {
			mcx -= 6;
			mcx -= mcx / 3;
			curMode = EDITSIDE_HEX;
			addr2 = topAddr * 2 + mcy * 32 + mcx;
			moveCursor2(addr2);
			repaint();
		}
		if ((mcx >= 58) && (mcx <= 73)) {
			mcx -= 58;
			curMode = EDITSIDE_ASCII;
			addr2 = topAddr * 2 + mcy * 32 + mcx * 2;
			moveCursor2(addr2);
			repaint();
		}
		if (event->button() == Qt::MiddleButton) {
			if ((addr2 >= 0) && (addr2 < maxAddr*2)) {
				avemu->SetBreak(typeMem, addr2 >> 1, avemu->GetBreak(typeMem, addr2 >> 1) ? 0 : AdViEmulator::TRAP_ALL);
				repaint();
			}
		}
	}
}

void MemoryViewBox::mouseDoubleClickEvent(QMouseEvent *event)
{
	int mcx = (event->pos().x() - 5) / this->fontMetrics().width('0');
	int mcy = (event->pos().y() - 5) / this->fontMetrics().height() - 1;
	if ((mcy >= 0) && (mcy < numLines)) {
		int addr2 = -1;
		if ((mcx >= 6) && (mcx <= 52)) {
			mcx -= 6;
			mcx -= mcx / 3;
			curMode = EDITSIDE_HEX;
			addr2 = topAddr * 2 + mcy * 32 + mcx;
			moveCursor2(addr2);
			repaint();
		}
		if ((mcx >= 58) && (mcx <= 73)) {
			mcx -= 58;
			curMode = EDITSIDE_ASCII;
			addr2 = topAddr * 2 + mcy * 32 + mcx * 2;
			moveCursor2(addr2);
			repaint();
		}
		if (event->button() == Qt::LeftButton) {
			if ((addr2 >= 0) && (addr2 < maxAddr*2)) {
				avemu->SetBreak(typeMem, addr2 >> 1, avemu->GetBreak(typeMem, addr2 >> 1) ? 0 : AdViEmulator::TRAP_ALL);
				repaint();
			}
		}
	}
}

void MemoryViewBox::scrollChanged(int value)
{
	if (scrollProc) setTopAddress(value * 16);
}
